using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
#if UNITY_EDITOR
using Sirenix.OdinInspector;
using UnityEngine.Serialization;
#endif

namespace Hsenl {
    /* 逻辑线组件（核心思路，针对异步逻辑的时间不确定性，只要给这些逻辑打上逻辑线，那么代码在很大程度上都会按照正确的执行顺序去执行）
     * 它的目的是让一整条异步代码的逻辑，转换成更像同步代码，让我们在调用它们的时候，不用太过纠结于他们的执行顺序
     *
     * 现在有 a b c三个执行指令，顺序就是abc顺序，对应的执行编号为 1 2 3
     * 同步调用情况
     * a b c   -- 最标准的方式，a直接开始执行，b等待a执行完，c等b执行完
     * b c a   -- a会直接执行，而b因为调用在a前，导致他也已经开始执行了，待b执行完调用Notify时，如果a还没执行完，他会把自己的任务交给a，待a执行完后，会帮b去触发c
     * b b c a -- 跟上面一样，a执行完后，会帮b触发第二个b的wait，然后第二个b完成，再去触发c的wait
     * a c b c -- 因为 cb被调用时，a还没执行完毕，所以后面无论怎么调用，都无所谓，最终还是会按照正确的顺序执行
     * c a b   -- a和c会同时执行，但b会等到a执行完
     *
     * 结合HTask一起使用, 可以让多线程逻辑不再并行执行, 而是按照我们的需要的执行顺序执行, 从而避免多线程带来的数据修改冲突问题, 同时代码也确实是在多线程上运行, 不占用主线程算力资源.
     */

    public class HTaskLine {
        public LineConfig Config { get; private set; } = new();

        // 这里其实不用对字段进行线程安全处理, 因为这个taskline在使用时就是为了避免多线程竞争, 但为了避免误用, 所以还是加上安全处理.
#if UNITY_EDITOR
        [ShowInInspector, ReadOnly, PropertySpace]
#endif
        private readonly ConcurrentDictionary<int, WaitBox> _waits = new();

        private int _currentLowestPosition = int.MaxValue; // 当前最低位置
        private int _highestPosition = int.MinValue; // 总共最高位置

        private readonly SemaphoreSlim _asyncLock = new(1, 1); // taskline一般使用在临界区比较长的地方, 且竞争比较激烈, 所以, 我们使用sema, 而不是lock

        public void SetConfig(LineConfig config) {
            this.Config = config;
        }

        /// <summary>
        /// 等待
        /// </summary>
        /// <param name="position"></param>
        /// <param name="waitSelf">如果自己的编号处于执行中状态，那么再调用自己的编号也会被Wait, 比如重复的wait 1，那么第二次wait就会等第一次wait结束再执行.
        /// 相反, 则不会等待, 而是直接执行. 前提条件是, 拦住你的必须是你自己, 如果你前面有更低的 position, 那无论true false, 自己都会被 wait</param>
        /// <param name="cover">覆盖之前的task，也就代表之前的 wait 将不在起效果，但如果已经正在执行了，则没有效果.
        /// 这种情况适用于, 比如播放动画，我现在先后调用了播放idle、attack两个动画，等执行的时候，idle便不再有必要去播放一次了，直接覆盖为播放attack就可以了</param>
        /// <param name="clearHigh">清除所有高位，例如 12345，每次调用1，都会把 2345 的 task 给清理掉.
        /// 这种情况适用于, 比如 23456 的操作都依赖于 1 必须是最新的, 所以如果 1 wait了, 就说明 1 要更新了, 那 23456 就没有必要再执行过时的代码了, 直接都取消掉, 等后续再重新 wait.</param>
        /// <returns></returns>
        public async HTask<TaskLineWaiter> Wait(int position, bool waitSelf = true, bool cover = false, bool clearHigh = false) {
            await this._asyncLock.WaitAsync().ConfigureAwait(false);

            HTask task;
            WaitBox waitBox;
            try {
                if (position > this._highestPosition) {
                    this._highestPosition = position;
                }

                byte positionStatus;
                if (position < this._currentLowestPosition) {
                    positionStatus = 0;
                }
                else if (position == this._currentLowestPosition) {
                    positionStatus = 1;
                }
                else {
                    positionStatus = 2;
                }

                if (!this._waits.TryGetValue(position, out waitBox)) {
                    waitBox = WaitBox.Create(); // 这里其实用不用池也行，因为box添加后，在整个wait line生命周期里，都不会删除了, 因为没必要删除, 后续还会再 wait
                    this._waits[position] = waitBox;
                }

                waitBox.waitSelf = waitSelf;

                if (clearHigh) {
                    this.Cancel(position + 1, this._highestPosition);
                }

                switch (positionStatus) {
                    case 0: {
                        // 小于就直接走
                        this._currentLowestPosition = position;
                        goto PASS;
                    }

                    case 1: {
                        if (waitSelf) {
                            if (waitBox.Tasks.Count != 0)
                                goto WAIT;
                        }

                        goto PASS;
                    }

                    case 2: {
                        // position 大于当前的最低 position, 那没什么好说的, 直接 wait
                        goto WAIT;
                    }
                }

                PASS:
                // 如果配置里, 指定了固定最小 position, 说明用户的目的是, 只有 <= 该固定值的 position, 才会放行, 否则, 一律 wait
                if (position > this.Config.fixedLowestPosition) {
                    goto WAIT;
                }

                // 放行, 不必等待
                waitBox.status = WaitBoxStatus.Executing;
                // this._asyncLock.Release();
                goto RETURN;

                WAIT:
                // 如果选择覆盖，则第二次wait，会把第一次wait给取消掉，也就是上一个wait后面的所有回调内容都不会再被执行，彻底哑火
                // 但如果覆盖之前，这些回调内容就已经被执行了，那这段就相当于是无效代码
                // 例如下面这个完整的示例
                // await LogicLine  -- 1
                // do...            -- 2
                // do...            -- 3
                // await 其他未知内容 -- 4
                // do...            -- 5
                // notify LogicLine -- 6
                // 假设我们现在连续调用了2次该示例
                // 如果选择不覆盖，当第一步的await等待完成后，第二步到后面的内容会被执行2次
                // 如果选择覆盖，那么只会执行一次，也就是第二次我们调用的那次
                // 然而，如果我们第二次调用该示例时，第一次的await已经等待完毕，并且执行到了第四步的那个await了，
                // 那么第二次所谓的覆盖自然也就无效了，因为上次的task已经正在执行并已经被清理过了，但是第三次的覆盖依然会生效.
                if (cover) {
                    waitBox.CancelAll();
                }

                // 如果之前的状态是结束了，那么就再次进入等待状态，之所以要判断，是因为有一种情况，连续wait 1两次，这种情况下，
                // 第二次wait 1的时候，其实1的状态是执行中的状态，此时如果把1设置为waiting，会导致第一次的wait，无法顺利的notify,
                // 导致后面的链断掉
                if (waitBox.status == WaitBoxStatus.Notify) {
                    waitBox.status = WaitBoxStatus.Waiting;
                }

                task = HTask.Create();
                waitBox.Tasks.Enqueue(task);
                if (waitBox.Tasks.Count > 100) {
                    Log.Warning("logic wait too many times, check whether it is normal"); // 可根据实际使用过程中调整警告阈值
                }

                // this._asyncLock.Release(); // 挂起前释放锁, 下面await后面的代码, 是由 Notify 触发的, 所以 Notify 执行完后自会释放锁, 不用这里操心
            }
            finally {
                this._asyncLock.Release();
            }

            await task;

            // 到这里说明等待完毕了
            // 例如 await LogicLine.Wait<T>(2); 这句代码里的await，等的就是上面的这个await
            // 接下来进入执行中的状态
            // 同时标志着，当前的定位在2这里, 此时这里的代码都是在 Notify 的执行块里, 所以, 这里是上锁的状态, 此时新的 wait 是进不来的, 不必担心设置 position 的冲突
            waitBox.status = WaitBoxStatus.Executing;
            this._currentLowestPosition = position;

            RETURN:
            return new TaskLineWaiter(this, position);
        }

        private async hvoid Notify(int position) {
            await this._asyncLock.WaitAsync().ConfigureAwait(false);
            try {
                if (position > this._highestPosition) {
                    throw new Exception($"<未知的Notify, 或TaskLine已经被清除了> Position:{position}'");
                }

                if (position > this._currentLowestPosition) {
                    // 说明在自己执行的这个过程中，又有比自己编号靠前的编号被wait了，这种情况，把自己状态设置下，然后自己走开就行了，把自己的任务
                    // 交给他处理
                    // 注意，这里自己的任务指的是触发下一个编号的task，而自己的task其实是空的，因为自己既然已经执行到notify这一步，
                    // 说明自己的task一定已经被调用过了

                    // 这里必须要设置状态，因为此时的状态一定是执行状态，如果不设置的话，靠前编号执行完开始轮询的时候，会发现这里仍然在执行，他就会
                    // 不管不问，直接退出，从而导致链条在这里断掉
                    var waitBox = this._waits[position];
                    waitBox.status = WaitBoxStatus.Notify;
                    return;
                }

                CirculationSetResult(this, position, this._highestPosition);
            }
            finally {
                this._asyncLock.Release();
            }
        }

        private void Cancel(int startPosition, int endPosition) {
            var position = startPosition;
            while (position <= endPosition) {
                if (this._waits.TryGetValue(position, out var logicBox)) {
                    logicBox.CancelAll();
                }

                position++;
            }
        }

        private static void CirculationSetResult(HTaskLine taskLine, int position, int max) {
            var own = true; // 第一次要获得是自己的task
            while (position <= max) {
                if (taskLine._waits.TryGetValue(position, out var waitBox)) {
                    try {
                        // 自己的状态，只有自己能设置为 Notify，也必然会设置
                        if (own) {
                            waitBox.status = WaitBoxStatus.Notify;
                        }

                        // task不为空，这次的循环就到这里为止
                        if (waitBox.Tasks.Count != 0) {
                            // 如果是own，则状态一定会是notify，说明当前task肯定不是own，不是自己，且还在执行状态，说明这是这种情况如下
                            // 例如 abc 逻辑线
                            // 先调用了b，再调用了a，导致b和a一起同时开始执行了，然后等a执行完了，b还再执行中，此时这种情况，
                            // 什么都不用做，就直接退出就好了，等b执行完后，会接着往下处理
                            if (waitBox.status == WaitBoxStatus.Executing) {
                                return;
                            }

                            if (waitBox.waitSelf) {
                                // 如果是wait self，那就一个个执行，例如
                                // a b b b这样调用，a执行完毕后，轮询到第二个b这，然后触发第二个b的wait，然后再等到第二个b执行完，
                                // 再触发第三个b的wait，这么依次触发
                                waitBox.SetResult();
                            }
                            else {
                                // 如果不是wait self的话，那么就直接把上面 三个b的wait全部触发了.
                                waitBox.SetResultAll();
                            }

                            return;
                        }
                    }
                    catch (Exception e) {
                        Log.Error(e);
                    }
                }

                // 当前的position没有可以执行的task，所以++到下个编号，继续尝试
                own = false;
                position++;
            }

            // 确保 position > max 时，也不会漏掉这次的最低位置设置.
            // 因为在 setResult 的时候, 会把 lowest 设置成自己的 position, 但如果 position 已经遍历到 > max position, 依然
            // 没有遇到需要 SetResult 的 task 的时候, 自然就不会设置了, 所以如果上面的循环里没设置的话, 那就这里设置.
            taskLine._currentLowestPosition = position;
        }

        public void Dispose() {
            this._currentLowestPosition = int.MaxValue;
            this._highestPosition = int.MinValue;
            foreach (var kv in this._waits) {
                kv.Value.Dispose();
                ObjectPool.Return(kv.Value);
            }

            this._waits.Clear();
        }

        public class LineConfig {
            public int fixedLowestPosition = int.MaxValue; // 如果设置了该值, 则 wait 的 position 只有小于等于该值的时候, 才会被通过, 否则都会被 wait
        }

        private enum WaitBoxStatus {
            Waiting, // 等待执行
            Executing, // 正在执行中
            Notify, // 执行完了
        }

        [Serializable]
        private class WaitBox : IDisposable {
            public WaitBoxStatus status;
            public bool waitSelf;
            private Queue<HTask> _tasks;

            public Queue<HTask> Tasks => this._tasks ??= new Queue<HTask>();

            public static WaitBox Create() {
                var box = ObjectPool.Rent<WaitBox>();
                return box;
            }

            public void SetResult() {
                try {
                    this.Tasks.Dequeue().SetResult();
                }
                catch (Exception e) {
                    Log.Error(e);
                }
            }

            public void SetResultAll() {
                while (this.Tasks.Count > 0) {
                    try {
                        this.Tasks.Dequeue().SetResult();
                    }
                    catch (Exception e) {
                        Log.Error(e);
                    }
                }
            }

            public void CancelAll() {
                while (this.Tasks.Count > 0) {
                    try {
                        this.Tasks.Dequeue().Abort();
                    }
                    catch (Exception e) {
                        Log.Error(e);
                    }
                }
            }

            public void Dispose() {
                this.status = 0;
                this.waitSelf = false;
                this._tasks?.Clear();
            }
        }

        public struct TaskLineWaiter : IDisposable {
            private HTaskLine _taskLine;
            private int _position;

            public bool IsDisposed => this._taskLine == null;

            public TaskLineWaiter(HTaskLine taskLine, int position) {
                this._taskLine = taskLine;
                this._position = position;
            }

            public void Dispose() {
                if (this.IsDisposed)
                    return;

                this._taskLine!.Notify(this._position).Tail();
                this._taskLine = null;
                this._position = 0;
            }
        }
    }
}